﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using lucere.exception;

namespace lucere.io.concurrency
{
	public class NativeFSLock : Lock, INativeFSLock
	{
		private static readonly HashSet<string> LockHeld = new HashSet<string>();
		private readonly DirectoryInfo _lockDir;
		private readonly FileInfo _path;
		private FileStream _channel;
		private FileStream _lock;
		private FileStream _f;
		private bool lockTaken;

		public NativeFSLock(DirectoryInfo lockDir, string lockName)
		{
			_lockDir = lockDir;
			_path = new FileInfo(Path.Combine(lockDir.FullName, lockName));
		}


		/// <summary>
		/// Attempts to obtain exclusive access and immediately return
		/// upon success or failure.
		/// </summary>
		/// <returns>true iff exclusive access is obtained</returns>
		public override bool Obtain()
		{
			if (LockExists())
				// Our instance is already locked:
				return false;

			if (!_lockDir.Exists)
				_lockDir.Create();

			string canonicalPath = _path.FullName;

			bool markedHeld = false;

			try
			{
				lock (LockHeld)
				{
					if (LockHeld.Contains(canonicalPath))
					{
						// Someone else in this JVM already has the lock:
						return false;
					}
					else
					{
						LockHeld.Add(canonicalPath);
						markedHeld = true;
					}
				}
				try
				{
					_f = new FileStream(canonicalPath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
				}
				catch (IOException)
				{
					_f = null;
				}

				if (_f != null)
				{
					try
					{
						_channel = _f;
						try
						{
							_channel.Lock(0, _channel.Length);
							lockTaken = true;
						}
						catch (IOException)
						{

						}
						finally
						{
							if (lockTaken == false)
							{
								try
								{
									_channel.Close();
								}
								finally
								{
									_channel = null;
								}
							}
						}
					}
					catch (IOException)
					{

					}
					finally
					{
						if (_channel == null)
						{
							try
							{
								_f.Close();
							}
							finally
							{
								_f = null;
							}
						}
					}
				}
			}
			catch (IOException)
			{

			}
			finally
			{
				if (markedHeld && !LockExists())
				{
					lock (LockHeld)
					{
						if (LockHeld.Contains(canonicalPath))
							LockHeld.Remove(canonicalPath);
					}
				}
			}
			return LockExists();
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		private bool LockExists()
		{
			return lockTaken;
		}

		/// <summary>
		/// Releases exclusive access.
		/// </summary>
		[MethodImpl(MethodImplOptions.Synchronized)]
		public override void Release()
		{
			if(LockExists())
			{
				try
				{
					_channel.Unlock(0,_channel.Length);
				}
				finally
				{
					lockTaken = false;
					try
					{
						_channel.Close();
					}
					finally
					{
						_channel = null;
						try
						{
							_f.Close();
						}
						finally
						{
							_f = null;
							lock (LockHeld)
							{
								LockHeld.Remove(_path.FullName);
							}
						}
					}
				}
				bool tmpBool;
				if (_path.Exists)
				{
					_path.Delete();
					tmpBool = true;
				}
				else if (Directory.Exists(_path.FullName))
				{
					Directory.Delete(_path.FullName);
					tmpBool = true;
				}
				else
					tmpBool = false;
				if (!tmpBool)
					throw new LockReleaseFailedException("failed to delete " + _path);
			}
			else
			{
				// if we don't hold the lock, and somebody still called release(), for
				// example as a result of calling IndexWriter.unlock(), we should attempt
				// to obtain the lock and release it. If the obtain fails, it means the
				// lock cannot be released, and we should throw a proper exception rather
				// than silently failing/not doing anything.
				bool obtained = false;
				try
				{
					if (!(obtained = Obtain()))
					{
						throw new LockReleaseFailedException(
							"Cannot forcefully unlock a NativeFSLock which is held by another indexer component: "
								+ _path);
					}
				}
				finally
				{
					if (obtained)
					{
						Release();
					}
				}
			}
		}

		/// <summary>
		/// Returns true if the resource is currently locked.  Note that one must
		/// still call <see cref="ILock.Obtain()"/> before using the resource.
		/// </summary>
		/// <returns></returns>
		public override bool IsLocked()
		{
			// The test for is isLocked is not directly possible with native file locks:

			// First a shortcut, if a lock reference in this instance is available
			if (LockExists()) return true;

			// Look if lock file is present; if not, there can definitely be no lock!
			if (!_path.Exists) return false;

			// Try to obtain and release (if was locked) the lock
			try
			{
				bool obtained = Obtain();
				if (obtained) Release();
				return !obtained;
			}
			catch (IOException ioe)
			{
				return false;
			}    
		}

		public override String ToString()
		{
			return "NativeFSLock@" + _path;
		}
	}
}